#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import logging
import os
import shutil
import sys
import tempfile
import yaml
import traceback

from oriolus.docker.docker.remove import RemoveCommand
from oriolus.docker.docker.requirements import InstallRequirements, RemoveRequirements, RemoveErrorType
from oriolus.docker.docker.search import SearchCommand
from oriolus.docker.docker.setup import SetupCommand
from oriolus.docker.repos.repository import Repository
from oriolus.docker.scripts.emptyrunner import EmptyRunner
from oriolus.docker.scripts.runner import Runner
from oriolus.docker.storages.filestorage import FileStorage
from oriolus.docker.storages.memorystorage import MemoryStorage
from oriolus.pith.checker.checker import Checker
from oriolus.pith.packets.configreader import ConfigReader, ConfigReaderError
from oriolus.pith.packets.packet import FilePacket, RemovePacket, Status

def __logerror__(logger, message):
    if logger.isEnabledFor(logging.DEBUG):
        logger.exception(message)
    else:
        logger.error(message)

class DockerError(Exception):
    def __init__(self, message):
        Exception.__init__(self, message)
        
class DockerFactoryError(Exception):
    def __init__(self, message):
        Exception.__init__(self, message)

class DockerRemoveError(DockerError):
    def __init__(self, message, dependent_packet_full_names):
        DockerError.__init__(self, message)
        self.dependent_packet_full_names = dependent_packet_full_names

class DockerRemoveErrorFactory():
    def __init__(self):
        self.__mapping_table = {RemoveErrorType.NoPacket: self.__get_docker_error,
                                RemoveErrorType.HasChildPackets: self.__get_docker_remove_error}

    def __get_docker_error(self, packet_name, message, data):
        return DockerError("Packet %s can not be removed. Reason: %s" % (packet_name, message))

    def __get_docker_remove_error(self, packet_name, message, data):
        return DockerRemoveError("Packet %s can not be removed. Reason: %s" % (packet_name, message), data)

    def create(self, reason):
        action = self.__mapping_table[reason.type]

        return action(reason.packet_name, reason.message, reason.data)   

class Docker:
    def __init__(self, repository, runner=Runner(), checker = Checker()):
        self.__rep = repository
        self.__runner = runner
        self.__checker = checker
        self.__install_requirements = InstallRequirements()
        self.__remove_requirements = RemoveRequirements()   
        self.__logger = logging.getLogger("Docker")      

    def install(self, packet_path, base_path=None, args=None, status=Status.INSTALLING):
        if packet_path in [None, ""]:
            message = "Parameter 'packet_path' is not defined"
            __logerror__(self.__logger, message)
            raise DockerError(message)

        if not self.__checker.check_archive_packet(packet_path):
            message = "Packet has invalid format"
            __logerror__(self.__logger, message)
            raise DockerError(message)

        packet = FilePacket(packet_path, base_path)
        can_install, reason = self.__install_requirements.check(self.__rep, packet)

        if not can_install:
            packet_in_rep = self.__rep.find_packet_by_fullname(packet.name)
            if not packet_in_rep in [None, []]:  
                if packet_in_rep.status == Status.INSTALLED_AS_DEPENDENCY and status == Status.INSTALLING:
                    #packet_in_rep.status = Status.status_map[status]
                    self.__rep.update_status(packet_in_rep.name, Status.status_map[status])
                    return
                
            message = "Packet %s can not be installed. Reason: %s" % (packet.name, str(reason))
            __logerror__(self.__logger, message)
            raise DockerError(message)

        command = SetupCommand(self.__runner, setup_status=status)
        try:
            self.__logger.debug("Packet '%s' will be installed" % packet.name)
            command.execute(packet, args)
        except Exception, ex:
            message = "Installation failed: %s" % ex.message
            __logerror__(self.__logger, message)
            raise DockerError(message)
        finally:
            self.__rep.update(packet)
            packet.dispose()

    def remove(self, packet_name=None, args=None):
        if packet_name in [None, ""]:
            message = "Parameter 'packet_name' is not defined"
            __logerror__(self.__logger, message)
            raise DockerError(message)

        can_remove, reason = self.__remove_requirements.check(self.__rep, packet_name)

        if not can_remove:
            message = "Packet %s can not be removed. Reason: %s" % (packet_name, str(reason.message))
            __logerror__(self.__logger, message)
            raise DockerRemoveErrorFactory().create(reason)

        source_packet = self.__rep.find_packet_by_fullname(packet_name)
        packet_to_remove = RemovePacket(source_packet)
        command = RemoveCommand(self.__runner)
        command.execute(packet_to_remove, args)
        self.__rep.remove(packet_name)

    def search(self, **kwargs):
        command = SearchCommand(self.__rep)

        return command.execute(**kwargs)

    def extract_storage(self):
        return self.__rep.storage

class DockerFactory:
    #Singleton - http://code.activestate.com/recipes/52558/
    class __impl:        
        def __init__(self, reader):
            self.__reader = reader
            self.__location_table_win = {"all_users": "ALLUSERSPROFILE", "user": "USERPROFILE"}
            self.__location_table_nix = {"user": "HOME"}
            self.__logger = logging.getLogger("DockerFactory")
            
        def get_docker(self, config_path = None):
            if not config_path:                                       
                config_path = os.path.join(sys.exec_prefix, "Scripts", "docker_config.yaml")
            if not os.path.exists(config_path):
                message = "Not found file '%s' for docker configuration!" % config_path                 
                __logerror__(self.__logger, message)
                raise DockerFactoryError(message)

            rep = self.__read_config(config_path)

            return Docker(rep)

        def get_virtual_docker(self, docker):
            existing_storage = docker.extract_storage()
            memory_storage = MemoryStorage()
            for packet in existing_storage.entries:
                memory_storage.add(packet)
            virtual_repository = Repository(memory_storage)
            empty_runner = EmptyRunner()

            return Docker(virtual_repository, runner=empty_runner)

        def __read_config(self, filepath):            
            dict = self.__reader.read(filepath)
            self.__logger.debug("Configuration file '%s' will be used for docker" % filepath)
            
            if "repository_folder" in dict.keys():
                folder = dict["repository_folder"]
                self.__logger.debug("repository_folder = %s" % folder)
            if "repository_location" in dict.keys():
                location = dict["repository_location"]
                self.__logger.debug("repository_location = %s" % location)
            if "repository_type" in dict.keys():
                type = dict["repository_type"]
                self.__logger.debug("repository_type = %s" % type)
            
            if sys.platform == "win32":
                location_path = os.environ[self.__location_table_win[location]]                
            else:
                if location == "all_users":
                    message = "Configuration option: %s for platform: %s is not supported" % (location ,sys.platform)
                    __logerror__(self.__logger, message)
                    raise DockerFactoryError (message)                
                location_path =  os.environ[self.__location_table_nix[location]]

            filestorage = FileStorage(os.path.join(location_path, folder))

            return Repository(filestorage)

    __instance = None

    def __init__(self, reader = ConfigReader()):
        if DockerFactory.__instance is None:
            DockerFactory.__instance =  DockerFactory.__impl(reader)

        self.__dict__['_DockerFactory__instance'] = DockerFactory.__instance

    def __getattr__(self, attr):
        return getattr(self.__instance, attr)

    def __setattr__(self, attr, value):
        return setattr(self.__instance, attr, value)